/*
 * SPDX-FileCopyrightText: 2022 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test.format

import android.media.MediaCodec
import android.media.MediaCodecList
import android.media.MediaFormat
import android.util.Log
import java.lang.Integer.min
import java.nio.ByteBuffer

/**
 * 创建基于 [MediaCodec] 的编码器以处理指定格式
 *
 * @param mediaFormat 由 [Format.getMediaFormat] 返回的 [MediaFormat] 实例
 * @param container 用于存储编码音频流的容器
 *
 * @throws Exception 如果设备不支持使用格式中设置的参数进行编码，
 * 或者配置 [MediaCodec] 失败
 */
class MediaCodecEncoder(
    mediaFormat: MediaFormat,
    private val container: Container,
) : Encoder(mediaFormat) {
    private val codec = createCodec(mediaFormat)  // MediaCodec 实例
    private val bufferInfo = MediaCodec.BufferInfo()  // 缓冲区信息对象
    private var trackIndex = -1  // 轨道索引

    /**
     * 启动编码器
     */
    override fun start() =
        codec.start()

    /**
     * 停止编码器
     */
    override fun stop() =
        codec.stop()

    /**
     * 释放编码器资源
     */
    override fun release() =
        codec.release()

    /**
     * 编码音频数据
     * 
     * @param buffer 包含PCM数据的字节缓冲区
     * @param isEof 是否为最后一帧数据
     */
    override fun encode(buffer: ByteBuffer, isEof: Boolean) {
        while (true) {
            var waitForever = false  // 是否无限期等待

            // 从编码器获取输入缓冲区
            val inputBufferId = codec.dequeueInputBuffer(TIMEOUT)
            if (inputBufferId >= 0) {
                val inputBuffer = codec.getInputBuffer(inputBufferId)!!
                // 计算最大不会溢出且为帧大小倍数的缓冲区大小
                val toCopy = min(buffer.remaining(), inputBuffer.remaining()) / frameSize * frameSize

                // 临时更改缓冲区限制以避免溢出
                val oldLimit = buffer.limit()
                buffer.limit(buffer.position() + toCopy)
                inputBuffer.put(buffer)
                buffer.limit(oldLimit)

                // 如果整个缓冲区已被消费，则提交EOF
                val flags = if (isEof && !buffer.hasRemaining()) {
                    Log.d(TAG, "在最后一个缓冲区上；提交EOF")
                    waitForever = true
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                } else {
                    0
                }

                // 将输入缓冲区排队到编码器
                codec.queueInputBuffer(inputBufferId, 0, toCopy, timestampUs, flags)

                // 更新已编码的帧数
                numFrames += toCopy / frameSize
            } else {
                Log.w(TAG, "意外的输入缓冲区出队错误: $inputBufferId")
            }

            // 刷新编码器的待处理编码数据到容器
            flush(waitForever)

            // 如果没有剩余数据则退出循环
            if (!buffer.hasRemaining()) {
                break
            }
        }
    }

    /** 
     * 将 [MediaCodec] 的待处理编码数据刷新到 [container]
     */
    private fun flush(waitForever: Boolean) {
        while (true) {
            // 根据是否等待永远设置超时时间
            val timeout = if (waitForever) { -1 } else { TIMEOUT }
            // 从编码器获取输出缓冲区
            val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, timeout)
            if (outputBufferId >= 0) {
                val buffer = codec.getOutputBuffer(outputBufferId)!!

                // 将编码后的样本数据写入容器
                container.writeSamples(trackIndex, buffer, bufferInfo)

                // 释放输出缓冲区
                codec.releaseOutputBuffer(outputBufferId, false)

                // 如果收到流结束标志，则完全刷新
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    Log.d(TAG, "收到EOF；完全刷新")
                    // 输出已完全写入
                    break
                }
            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // 输出格式已更改
                val outputFormat = codec.outputFormat
                Log.d(TAG, "输出格式更改为: $outputFormat")
                trackIndex = container.addTrack(outputFormat)
                container.start()
            } else if (outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // 稍后重试
                break
            } else {
                // 意外的输出缓冲区出队错误
                Log.w(TAG, "意外的输出缓冲区出队错误: $outputBufferId")
                break
            }
        }
    }

    companion object {
        private val TAG = MediaCodecEncoder::class.java.simpleName
        private const val TIMEOUT = 500L  // 超时时间（毫秒）

        /**
         * 创建 MediaCodec 实例
         * 
         * @param mediaFormat 媒体格式
         * @return MediaCodec 实例
         * @throws Exception 如果找不到合适的编码器
         */
        fun createCodec(mediaFormat: MediaFormat): MediaCodec {
            // 查找适合指定格式的编码器
            val encoder = MediaCodecList(MediaCodecList.REGULAR_CODECS).findEncoderForFormat(mediaFormat)
                ?: throw Exception("未找到适合 $mediaFormat 的编码器")
            Log.d(TAG, "音频编码器: $encoder")

            // 根据编解码器名称创建实例
            val codec = MediaCodec.createByCodecName(encoder)

            try {
                // 配置编码器
                codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            } catch (e: Exception) {
                // 配置失败时释放资源并重新抛出异常
                codec.release()
                throw e
            }

            return codec
        }
    }
}